package org.codefilarete.stalactite.engine.configurer.model;

import org.codefilarete.stalactite.engine.runtime.AssociationTable;
import org.codefilarete.stalactite.engine.runtime.IndexedAssociationTable;
import org.codefilarete.stalactite.sql.ddl.structure.Key;
import org.codefilarete.stalactite.sql.ddl.structure.KeyMapping;
import org.codefilarete.stalactite.sql.ddl.structure.Table;

public class IntermediaryRelationJoin<
		LEFTTABLE extends Table<LEFTTABLE>,
		RIGHTTABLE extends Table<RIGHTTABLE>,
		ASSOCIATIONTABLE extends AssociationTable<ASSOCIATIONTABLE, LEFTTABLE, RIGHTTABLE, LEFTJOINTYPE, RIGHTJOINTYPE>,
		LEFTJOINTYPE,
		RIGHTJOINTYPE
		> implements RelationJoin {
	
	public static <LEFTTABLE extends Table<LEFTTABLE>,
			RIGHTTABLE extends Table<RIGHTTABLE>,
			ASSOCIATIONTABLE extends IndexedAssociationTable<ASSOCIATIONTABLE, LEFTTABLE, RIGHTTABLE, LEFTJOINTYPE, RIGHTJOINTYPE>,
			LEFTJOINTYPE,
			RIGHTJOINTYPE
			> IntermediaryRelationJoin<LEFTTABLE, RIGHTTABLE, ASSOCIATIONTABLE, LEFTJOINTYPE, RIGHTJOINTYPE>
	from(ASSOCIATIONTABLE associationTable) {
		return new IntermediaryRelationJoin<>(associationTable.getOneSideKey(),
				associationTable.getOneSideForeignKey(),
				associationTable.getManySideForeignKey(),
				associationTable.getManySideKey());
	}
	
	
	private final KeyMapping<LEFTTABLE, ASSOCIATIONTABLE, LEFTJOINTYPE> leftKeyMapping;
	
	private final KeyMapping<ASSOCIATIONTABLE, RIGHTTABLE, RIGHTJOINTYPE> rightKeyMapping;
	
	/**
	 * Constructor that takes an {@link AssociationTable} as argument.
	 * A local type parameter {@code INTERMEDIARYTABLE} is introduced to satisfy both the {@code SELF}-referential
	 * bound of {@link AssociationTable} and the {@code ASSOCIATIONTABLE} type parameter of this class.
	 * The unchecked cast is safe because {@code INTERMEDIARYTABLE} is structurally the same table.
	 */
	public IntermediaryRelationJoin(ASSOCIATIONTABLE associationTable) {
		this(associationTable.getOneSideKey(),
				associationTable.getOneSideForeignKey(),
				associationTable.getManySideForeignKey(),
				associationTable.getManySideKey());
	}
	
	public IntermediaryRelationJoin(Key<LEFTTABLE, LEFTJOINTYPE> leftKey,
									Key<ASSOCIATIONTABLE, LEFTJOINTYPE> leftAssociationKey,
									Key<ASSOCIATIONTABLE, RIGHTJOINTYPE> rightAssociationKey,
									Key<RIGHTTABLE, RIGHTJOINTYPE> rightKey) {
		this.leftKeyMapping = new KeyMapping<>(leftKey, leftAssociationKey);
		this.rightKeyMapping = new KeyMapping<>(rightAssociationKey, rightKey);
	}
	
	public Key<LEFTTABLE, LEFTJOINTYPE> getLeftKey() {
		return leftKeyMapping.getSourceKey();
	}
	
	public Key<ASSOCIATIONTABLE, LEFTJOINTYPE> getLeftAssociationKey() {
		return leftKeyMapping.getReferencedKey();
	}
	
	public Key<ASSOCIATIONTABLE, RIGHTJOINTYPE> getRightAssociationKey() {
		return rightKeyMapping.getSourceKey();
	}
	
	public Key<RIGHTTABLE, RIGHTJOINTYPE> getRightKey() {
		return rightKeyMapping.getReferencedKey();
	}
	
	public ASSOCIATIONTABLE getJoinTable() {
		return leftKeyMapping.getReferencedKey().getTable();
	}
}
